package ace.cmp.security.oauth2.resource.server.core.web;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import lombok.SneakyThrows;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

/**
 * @author caspar
 * @date 2023/3/14 16:06 dynamic resolve input for oauth2 security.
 * use annotation {@link org.springframework.security.access.prepost.PreAuthorize} or {@link org.springframework.security.access.prepost.PostAuthorize}
 */
public class BearerTokenDynamicResolver implements BearerTokenResolver {
  private final BearerTokenResolver delegating;
  private final PathMatcher pathMatcher = new AntPathMatcher();
  private final List<String> authenticationUris;
  private final RequestMappingHandlerMapping handlerMapping;
  private static final List<Class> annotationClasses =
      Arrays.asList(PreAuthorize.class, PostAuthorize.class);

  public BearerTokenDynamicResolver(
      BearerTokenResolver delegating,
      List<String> authenticationUris,
      RequestMappingHandlerMapping handlerMapping) {
    this.delegating = delegating;
    this.authenticationUris = authenticationUris;
    this.handlerMapping = handlerMapping;
  }

  @SneakyThrows
  @Override
  public String resolve(HttpServletRequest request) {

    if (this.hasSecurityAnnotation(request)) {
      return this.delegating.resolve(request);
    }

    if (this.isAuthenticationUris(request)) {
      return this.delegating.resolve(request);
    }

    return null;
  }

  private boolean isAuthenticationUris(HttpServletRequest request) {
    return authenticationUris.stream()
        .anyMatch(url -> this.pathMatcher.match(url, request.getRequestURI()));
  }

  @SneakyThrows
  private boolean hasSecurityAnnotation(HttpServletRequest request) {
    Object handlerMethodObject =
        Optional.ofNullable(handlerMapping.getHandler(request))
            .map(p -> p.getHandler())
            .orElse(null);
    if (handlerMethodObject != null && handlerMethodObject instanceof HandlerMethod) {
      HandlerMethod handlerMethod = (HandlerMethod) handlerMethodObject;
      for (Class cls : annotationClasses) {
        // 获取方法上边的注解
        Object method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), cls);
        // 获取类上边的注解
        Object controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), cls);
        if (method != null || controller != null) {
          return true;
        }
      }
    }
    return false;
  }
}
